package org.helsoft.mojo.api;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * 
 * @author steg
 * 
 */
public class PluginDescriptor {
	public static final String BUNDLE_REGEX = "\\s*[^;,]+(\\s*;\\s*[^=;:]+:?=\"[^\";]+\")*\\s*";

	/**
	 * The manifest
	 */
	private Manifest manifest;

	/**
	 * The bundle version
	 */
	private String bundleVersion;
	
	/**
	 * The bundles
	 */
	private List<Bundle> bundles;
	
	/**
	 * The bundles classpath
	 */
	private List<String> classpath;
	
	/**
	 * Creates a plugin descriptor from a file. The file could either be a jar
	 * or a zip file, or a manifest. In the latter case the boolean
	 * <code>isManfiest</code> must be set.
	 * 
	 * @param file
	 *            The file
	 * @param isManifest
	 *            Must be set to <code>true</code> if <code>file</code> is a
	 *            manifest
	 * @throws IOException 
	 */
	private PluginDescriptor(File file, boolean isManifest) throws IOException {
		if (!isManifest) {
			if (file.getName().endsWith(".jar")) {
				initFromJar(file);
			} else if (file.getName().endsWith(".zip")) {
				initFromZip(file);
			} else if (file.isDirectory()) {
				initFromDir(file);
			}
		} else {
			initFromManifest(file);
		}
	}

	/**
	 * 
	 * @param manfiest
	 */
	private PluginDescriptor(Manifest manifest) {
		this.manifest = manifest;
	}
	
	private void initFromManifest(File file) throws IOException {
		InputStream input;
		input = new FileInputStream(file);
		try {
			manifest = new Manifest(input);
		} finally {
			try {
				input.close();
			} catch (Exception ex) {
				//ignore
			}
		}
	}

	private void initFromDir(File dir) throws IOException {
		File mf = new File(dir, "META-INF" + File.separator + "MANIFEST.MF");
		if (!mf.isFile()) {
			mf = new File(dir, "MANIFEST.MF");
		}
		if (mf.isFile()) {
			initFromManifest(mf);
		} else {
			throw new IOException("Manifest not found at " + dir);
		}
	}

	private void initFromZip(File file) throws IOException {
		ZipFile zFile = new ZipFile(file, ZipFile.OPEN_READ);
		ZipEntry entry = zFile.getEntry("META-INF/MANIFEST.MF");
		if (entry == null) {
			entry = zFile.getEntry("MANIFEST.MF");
		}
		if (entry != null) {
			InputStream input = zFile.getInputStream(entry);
			try {
				manifest = new Manifest(input);
			} finally {
				try {
					input.close();
				} catch (Exception ex) {
					//ignore
				}
			}
		} else {
			throw new IOException("Manifest not found in " + file);
		}
	}

	private void initFromJar(File file) throws IOException {
		manifest = new JarFile(file).getManifest();
	}
	
	/**
	 * Retrieves the manifest of the plugin.
	 * 
	 * @return The manifest
	 */
	public final Manifest getManifest() {
		return manifest;
	}
	
	/**
	 * 
	 * @return The bundle version
	 */
	public final String getBundleVersion() {
		if (bundleVersion == null) {
			bundleVersion = manifest.getMainAttributes().getValue("Bundle-Version");
		}
		return bundleVersion;
	}
	
	/**
	 * 
	 * @return The bundles
	 */
	public final List<Bundle> getBundles() {
		if (bundles ==  null) {
			bundles = new ArrayList<Bundle>();
			String bundleString = manifest.getMainAttributes().getValue(
			"Require-Bundle");
			if (bundleString != null) {
				Pattern pattern = Pattern.compile("(" + BUNDLE_REGEX + ")+");
				Matcher matcher = pattern.matcher(bundleString);
				while (matcher.find()) {
					String description = matcher.group(0).trim();
					if (!description.contains(":=")) {
						bundles.add(new Bundle(description));
					}
				}
			}
		}
		return Collections.unmodifiableList(bundles);
	}
	
	/**
	 * 
	 * @return The bundle classpath
	 */
	public final List<String> getBundleClasspath() {
		if (classpath == null) {
			String str = manifest.getMainAttributes().getValue("Bundle-Classpath");
			classpath = str == null ? new ArrayList<String>() : Arrays.asList(str.trim().split("\\s*,\\s*"));
		}
		return Collections.unmodifiableList(classpath);
	}
	
	/**
	 * 
	 * @param manifestFile
	 * @return
	 * @throws IOException
	 */
	public static PluginDescriptor fromManifest(File manifestFile) throws IOException {		
		return new PluginDescriptor(manifestFile, true);
	}
	
	/**
	 * 
	 * @param manifest Should not be <code>null</code>
	 * @return
	 */
	public static PluginDescriptor fromManifest(Manifest manifest) {
		return new PluginDescriptor(manifest);
	}
	
	/**
	 * 
	 * @param jarFile
	 * @return
	 * @throws IOException
	 */
	public static PluginDescriptor fromFile(File jarFile) throws IOException {
		return new PluginDescriptor(jarFile, false);
	}
}
